<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>001-管网绘图</title>
  <style>
    body {
      background-color: #000;
      text-align: center;
    }

    canvas {
      background-color: #fff;
    }
    
    #map {
      width: 800px;
      height: 800px;
      margin: 0 auto;
      overflow: auto;
    }
  </style>
</head>

<body>
  <div id="map"></div>

  <script>
    let canvas = null
    let context = null
    let width = 200
    let height = 200
    let realWidthPre = 0
    window.onload = function () {
      const canvasDivId = 'map'
      const canvasWidth = 800
      const canvasHeight = 400
      const canvasParams = [canvasDivId, canvasWidth, canvasHeight]
      initCanvas(canvasParams)

      const data = [
        { // 监测点
          name: '永安桥南液位计',              // 监测点名称
          liquidHeight: 1.513,                // 液位(m)
          wellDistance: 86,                   // 井间距离(m)
          pipeDiameter: 1100,                 // 管径或管高(mm)
          groundElevation: 4.100,             // 地面标高(m)
          pipeUpStreamElevation: 1.213,       // 上游管内底(m)
          pipeDownStreamElevation: -0.217     // 下游管内底(m)
        },
        {
          name: '永安桥南液位计',
          liquidHeight: 1.170,
          wellDistance: 44,
          pipeDiameter: 600,
          groundElevation: 5.472,
          pipeUpStreamElevation: 0.272,
          pipeDownStreamElevation: 0.217
        },
        {
          name: '永安桥倒虹',
          liquidHeight: 0.990,
          wellDistance: 30,
          pipeDiameter: 1200,
          groundElevation: 5.417,
          pipeUpStreamElevation: 0.067,
          pipeDownStreamElevation: -0.030
        },
        {
          name: '永安桥倒虹',
          liquidHeight: 0.870,
          wellDistance: 18,
          pipeDiameter: 1200,
          groundElevation: 4.649,
          pipeUpStreamElevation: -0.120,
          pipeDownStreamElevation: 0.140
        },
        {
          name: '晋陵路关河路',
          liquidHeight: 0.990,
          wellDistance: 35,
          pipeDiameter: 1000,
          groundElevation: 4.541,
          pipeUpStreamElevation: 0.241,
          pipeDownStreamElevation: -0.220
        },
        {
          name: '晋陵路关河路',
          liquidHeight: 0.580,
          wellDistance: 81,
          pipeDiameter: 1000,
          groundElevation: 4.437,
          pipeUpStreamElevation: -0.063,
          pipeDownStreamElevation: -0.018
        },
        {
          name: '晋陵路关河路液位计',
          liquidHeight: 0.520,
          wellDistance: 53,
          pipeDiameter: 1100,
          groundElevation: 5.382,
          pipeUpStreamElevation: -0.018,
          pipeDownStreamElevation: 0.212
        },
        {
          name: '晋陵路关河路液位计',
          liquidHeight: 0.550,
          wellDistance: 0,
          pipeDiameter: 0,
          groundElevation: 5.912,
          pipeUpStreamElevation: 0,
          pipeDownStreamElevation: 0
        }
      ]

      // 内边距系数
      const paddingScale = 10

      // 上下左右内边距
      const topPadding = canvasHeight / paddingScale / 4
      const bottomPadding = canvasHeight / paddingScale / 4
      const leftPadding = canvasWidth / paddingScale / 4
      const rightPadding = canvasWidth / paddingScale / 2

      // 图例宽度
      const legendWidth = canvasWidth / paddingScale * 2

      // 实际宽度。减去左右内边距与图例
      const factWidth = canvasWidth - leftPadding - rightPadding - legendWidth
      // 实际高度。减去上下内边距
      const factHeight = canvasHeight - topPadding - bottomPadding
      // x,y的起始坐标
      const startX = leftPadding
      const startY = topPadding
      
      // 最大标高，用于计算整个图形的高度比例
      let maxElevation = 0
      // 管径距离总和，用于计算整个图形的宽度比例
      let sumDistance = 0

      data.forEach((place, index) => {
        if (place.groundElevation > maxElevation) {
          maxElevation = place.groundElevation
        }
        if (index < data.length - 1) {
          sumDistance += place.wellDistance
        }
      })
      // 图形从上到下 分为 3 个部分，分别为 序号:图形主体:图形说明 = 1:5:4
      // 高度的比例系数如下：
      const heightScale = factHeight / (maxElevation * 2)
      const numHeight = factHeight / 10 * 2
      const graphHeight = factHeight / 10 * 5
      const descripHeight = factHeight / 10 * 3
      // 宽度的比例系数如下：
      const widthScale = factWidth / sumDistance

      // 图形主体X坐标起点
      const graphX = leftPadding + legendWidth

      // 黄海基准面Y坐标
      const elevationY = numHeight + graphHeight
      // 管径Y坐标
      const pipeDiameterY = numHeight + graphHeight + (descripHeight / 2 + descripHeight / 2 / 3)
      // 管段Y坐标
      const pipeSelectionY = numHeight + graphHeight + (descripHeight / 2 + descripHeight / 2 / 3 + descripHeight / 2 / 3 * 2)

      // 管径图例
      context.beginPath()
      context.moveTo(startX, pipeDiameterY)
      context.lineTo(startX + legendWidth - 10, pipeDiameterY)
      context.stroke()
      context.beginPath()
      context.moveTo(startX, pipeDiameterY - descripHeight / 2 / 4)
      context.lineTo(startX, pipeDiameterY + descripHeight / 2 / 4)
      context.stroke()
      context.beginPath()
      context.moveTo(startX + legendWidth - 10, pipeDiameterY - descripHeight / 2 / 4)
      context.lineTo(startX + legendWidth - 10, pipeDiameterY + descripHeight / 2 / 4)
      context.stroke()
      setTextDirection({
        context: context,
        x: startX,
        y: pipeDiameterY,
        text: '管道长度',
        direction: 'right-top',
        font: '10px sans-serif'
      })
      setTextDirection({
        context: context,
        x: startX,
        y: pipeDiameterY,
        text: '管径',
        direction: 'right-bottom',
        font: '10px sans-serif'
      })
      setTextDirection({
        context: context,
        x: startX + legendWidth - 10,
        y: pipeDiameterY,
        text: '管底名义坡降',
        direction: 'left-top',
        font: '10px sans-serif'
      })
      setTextDirection({
        context: context,
        x: startX + legendWidth - 10,
        y: pipeDiameterY,
        text: '管道连接状态',
        direction: 'left-bottom',
        font: '10px sans-serif'
      })

      // 管段图例
      context.beginPath()
      context.moveTo(startX, pipeSelectionY)
      context.lineTo(startX + legendWidth - 10, pipeSelectionY)
      context.stroke()
      context.beginPath()
      context.moveTo(startX, pipeSelectionY - descripHeight / 2 / 4)
      context.lineTo(startX, pipeSelectionY + descripHeight / 2 / 4)
      context.stroke()
      context.beginPath()
      context.moveTo(startX + legendWidth - 10, pipeSelectionY - descripHeight / 2 / 4)
      context.lineTo(startX + legendWidth - 10, pipeSelectionY + descripHeight / 2 / 4)
      context.stroke()
      setTextDirection({
        context: context,
        x: startX,
        y: pipeSelectionY,
        text: '上游管内底',
        direction: 'right-top',
        font: '10px sans-serif'
      })
      setTextDirection({
        context: context,
        x: startX + legendWidth - 10,
        y: pipeSelectionY,
        text: '下游管内底',
        direction: 'left-bottom',
        font: '10px sans-serif'
      })

      // 黄海基准面文本标识
      setTextDirection({
        context: context,
        x: graphX,
        y: elevationY,
        text: '±0.000',
        direction: 'left-center',
        font: '12px sans-serif',
        color: 'red'
      })

      let ground,                     // 地面标高
          liquid,                     // 液位
          pipe,                       // 管道
          // pipeTop,                    // 管道上沿
          // pipeBottom,                 // 管道下沿
          vertical,                   // 竖向：海面基准、管径、管段
          elevation,                  // 海面基准
          pipeDiameter,               // 管径
          pipeSelection,              // 管段
          nextX = graphX

      data.forEach((place, index) => {
        if (isNaN(place.groundElevation)) return

        // 统一X坐标
        const x = nextX
        const y = canvasHeight - bottomPadding
        if (!isNaN(place.wellDistance)) {
          nextX += place.wellDistance * widthScale
        }

        // 1. 地面标高Y坐标
        if (!isNaN(place.groundElevation)) {
          const groundY = numHeight + graphHeight - place.groundElevation * heightScale

          context.beginPath()
          context.moveTo(x, groundY)
          context.lineTo(x, y)
          context.strokeStyle = 'black'
          context.stroke()
          
          context.beginPath()
          context.moveTo(x, groundY - 25)
          context.lineTo(x, groundY - 40)
          context.strokeStyle = 'black'
          context.stroke()

          context.beginPath()
          context.moveTo(x - 10, groundY - 40)
          context.lineTo(x + 10, groundY - 40)
          context.strokeStyle = 'black'
          context.stroke()

          setTextDirection({
            context: context,
            x: x,
            y: groundY - 40,
            text: index + 1,
            direction: 'center-top',
            font: '10px sans-serif'
          })
          
          setTextDirection({
            context: context,
            x: x,
            y: groundY,
            text: place.groundElevation,
            direction: 'center-top',
            font: '10px sans-serif'
          })

          if (ground) {
            context.beginPath()
            context.moveTo(ground.x, ground.y)
            context.lineTo(x, groundY)
            context.strokeStyle = 'black'
            context.stroke()
          }

          ground = {
            x,
            y: groundY
          }
        }

        // 2. 液位Y坐标
        if (!isNaN(place.liquidHeight)) {
          const liquidY = numHeight + graphHeight - place.liquidHeight * heightScale

          if (liquid) {
            context.beginPath()
            context.moveTo(liquid.x, liquid.y)
            context.lineTo(x, liquidY)
            context.strokeStyle = 'blue'
            context.stroke()

            // 水力名义坡降
            const nominalHydraulicGradient = (data[index-1].liquidHeight - place.liquidHeight) / data[index-1].wellDistance * 1000

            setTextDirection({
              context: context,
              x: x,
              y: liquidY,
              text: nominalHydraulicGradient.toFixed(2),
              direction: 'right-top',
              font: '10px sans-serif'
            })
          }

          liquid = {
            x,
            y: liquidY
          }
        } else {
          liquid = null
        }

        let pipeDiameterDiff = 0
        if (index > 0) {
          pipeDiameterDiff = data[index-1].pipeDiameter - place.pipeDiameter
        }
        let pipeDiameterStatus = ''
        let pipeColor = 'black'
        if (pipeDiameterDiff !== 0) {
          if (pipeDiameterDiff > 0) {
            pipeDiameterStatus = '大管接小管'
          } else if (pipeDiameterDiff < 0) {
            pipeDiameterStatus = '逆坡'
          }
          pipeColor = 'red'
        }
        // 3. 管道上沿Y坐标
        if (pipe) {
          context.beginPath()
          context.moveTo(pipe.topX, pipe.topY)
          context.lineTo(x, pipe.nextTopY)
          context.strokeStyle = pipeColor
          context.stroke() 

          context.beginPath()
          context.moveTo(pipe.bottomX, pipe.bottomY)
          context.lineTo(x, pipe.nextBottomY)
          context.strokeStyle = pipeColor
          context.stroke()
        }
        if (
          !isNaN(place.pipeDiameter) &&
          place.pipeDiameter > 0 &&
          !isNaN(place.pipeUpStreamElevation) &&
          !isNaN(place.pipeDownStreamElevation)
        ) {
          const pipeTopY = numHeight + graphHeight - place.pipeUpStreamElevation * heightScale
          const pipeBottomY = numHeight + graphHeight - (place.pipeUpStreamElevation + place.pipeDiameter / 
          1000) * heightScale

          const nextPipeTopY = numHeight + graphHeight - place.pipeDownStreamElevation * heightScale
          const nextPipeBottomY = numHeight + graphHeight - (place.pipeDownStreamElevation + place.pipeDiameter / 1000) * heightScale

          pipe = {
            topX: x,
            topY: pipeTopY,
            bottomX: x,
            bottomY: pipeBottomY,
            nextTopY: nextPipeTopY,
            nextBottomY: nextPipeBottomY
          }
        } else {
          pipe = null
        }

        // 4. 海面基准、管径、管段
        if (index === 0) {
          vertical = {
            x,
            elevationY,
            pipeDiameterY,
            pipeSelectionY
          }
        } else if (index === data.length - 1 && vertical) {
            // 海面基准
            context.beginPath()
            context.moveTo(vertical.x, vertical.elevationY)
            context.lineTo(x, elevationY)
            context.strokeStyle = 'green'
            context.stroke()

            // 管径
            context.beginPath()
            context.moveTo(vertical.x, vertical.pipeDiameterY)
            context.lineTo(x, pipeDiameterY)
            context.strokeStyle = 'black'
            context.stroke()

            // 管段
            context.beginPath()
            context.moveTo(vertical.x, vertical.pipeSelectionY)
            context.lineTo(x, pipeSelectionY)
            context.strokeStyle = 'black'
            context.stroke()
        }
        
        // 管径-文字标注
        if (index > 0) {
          setTextDirection({
            context: context,
            x: pipeDiameter.x,
            y: pipeDiameter.y,
            text: data[index-1].wellDistance + 'm',
            direction: 'right-top',
            font: '8px sans-serif'
          })
          setTextDirection({
            context: context,
            x: pipeDiameter.x,
            y: pipeDiameter.y,
            text: 'DN' + data[index-1].pipeDiameter,
            direction: 'right-bottom',
            font: '8px sans-serif'
          })
          const nominalSlopePipeBottom = (data[index-1].pipeUpStreamElevation - data[index-1].pipeDownStreamElevation) / data[index-1].wellDistance * 1000
          if (place.pipeDiameter > 0) {
            setTextDirection({
              context: context,
              x: x,
              y: pipeDiameterY,
              text: nominalSlopePipeBottom.toFixed(2),
              direction: 'left-top',
              font: '8px sans-serif'
            })
            setTextDirection({
              context: context,
              x: x,
              y: pipeDiameterY,
              text: pipeDiameterStatus,
              direction: 'left-bottom',
              font: '8px sans-serif'
            })
          }
          
          setTextDirection({
            context: context,
            x: pipeSelection.x,
            y: pipeSelection.y,
            text: data[index-1].pipeUpStreamElevation,
            direction: 'right-top',
            font: '8px sans-serif'
          })
          setTextDirection({
            context: context,
            x: x,
            y: pipeSelectionY,
            text: data[index-1].pipeDownStreamElevation,
            direction: 'left-bottom',
            font: '8px sans-serif'
          })
        }
        elevation = {
          x,
          y: elevationY
        }
        pipeDiameter = {
          x,
          y: pipeDiameterY
        }
        pipeSelection = {
          x,
          y: pipeSelectionY
        }
      })
    }

    /**
     * 初始化canvas 信息
     */
    function initCanvas([canvasDomId, canvasWidth, canvasHeight]) {
      const canvasParent = document.getElementById(canvasDomId)
      if (canvas) {
        canvas.parentNode.removeChild(canvas)
      }
      canvas = document.createElement("canvas")
      canvasParent.appendChild(canvas)
      width = canvasWidth
      height = canvasHeight
      canvas.width = width
      canvas.height = height
      context = canvas.getContext("2d")
    }

    
    function setTextDirection(params) {
      params = Object.assign({
        context: null,
        text: '',
        x: null,
        y: null,
        margin: 5,
        font: '10px sans-serif',
        color: 'black',
        style: 'fill', // 'fill' 'stroke', 填充 边框
        direction: 'left-top' // 'left-top' 'left-center' 'left-bottom' 'center-top' 'center-center' 'center-bottom' 'right-top' 'right-center' 'right-bottom', 左上 左中 左下 中上 中中 中下 右上 右中 右下
      }, params);

      if (!params.context || params.text === '' || !params.x || !params.y) return

      params.context.font = params.font
      const size = params.context.measureText(params.text)
      const height = size.actualBoundingBoxAscent - size.actualBoundingBoxDescent
      switch(params.direction){
        case 'left-top':
          params.x = params.x - size.width - params.margin
          params.y = params.y - params.margin
          break
        case 'left-center':
          params.x = params.x - size.width - params.margin
          params.y = params.y + height / 2
          break
        case 'left-bottom':
          params.x = params.x - size.width - params.margin
          params.y = params.y + height + params.margin
          break
        case 'center-top':
          params.x = params.x - size.width / 2
          params.y = params.y - height
          break
        case 'center-center':
          params.x = params.x - size.width / 2
          params.y = params.y + height / 2
          break
        case 'center-bottom':
          params.x = params.x - size.width / 2
          params.y = params.y + height
          break
        case 'right-top':
          params.x = params.x + params.margin
          params.y = params.y - params.margin
          break
        case 'right-center':
          params.x = params.x + params.margin
          params.y = params.y + height / 2
          break
        case 'right-bottom':
          params.x = params.x + params.margin
          params.y = params.y + height + params.margin
          break
      }
      params.context.beginPath()
      params.context.fillStyle = params.color
      if (params.style === 'fill') {
        params.context.fillText(params.text, params.x, params.y)
      } else {
        params.context.strokeText(params.text, params.x, params.y)
      }
    }
  </script>
</body>

</html>